﻿using System;
using System.IO;
using System.IO.IsolatedStorage;
using System.Text;
using Liphofra.Core.Json;
using Newtonsoft.Json;

namespace Liphofra.Core.Storage
{
    /// <summary>
    /// An lean isolated storage abstraction that allows to store and retrieve data to and from isolated storage,
    /// using file name syntax.
    /// </summary>
    public class FileStorage : IDisposable
    {
        private IsolatedStorageFile _isolatedStorageFile;

        /// <summary>
        /// Initializes a new instance of the <see cref="FileStorage"/> class.
        /// </summary>
        public FileStorage()
        {
            _isolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication();
        }

        /// <summary>
        /// Persists the specified data to isolated storage using the given file name.
        /// </summary>
        /// <remarks>The given data must be Json serializable.</remarks>
        /// <typeparam name="T">The type of the data to persist.</typeparam>
        /// <param name="data">The data to persist.</param>
        /// <param name="fileName">Name of the file that is created or overwritten.</param>
        /// <exception cref="System.InvalidOperationException">Thrown when this instance has already been disposed.</exception>
        public void Persist<T>(T data, string fileName) where T : class
        {
            if (_isDisposed)
            {
                throw new InvalidOperationException("This instance has already been disposed.");
            }

            if (string.IsNullOrEmpty(fileName))
            {
                return;
            }

            if (_isolatedStorageFile.FileExists(fileName))
            {
                _isolatedStorageFile.DeleteFile(fileName);
            }

            if (data == null)
            {
                return;
            }

            var settings = CreateSerializerSettings();
            var serialized = JsonConvert.SerializeObject(data, settings);

            var file = _isolatedStorageFile.OpenFile(fileName, FileMode.Create, FileAccess.Write);
            using (var streamWriter = new StreamWriter(file, Encoding.UTF8))
            {
                streamWriter.Write(serialized);
            }
        }

        /// <summary>
        /// Retrieves the data from the specified file name.
        /// </summary>
        /// <typeparam name="T">The expected type of the data.</typeparam>
        /// <param name="fileName">The name of the file to retrieve the data from.</param>
        /// <returns>The retrieved and deserialized data from the specified file in isolated storage, or the default value for the given type if the file was not found.</returns>
        /// <exception cref="System.InvalidOperationException">Thrown when this instance has already been disposed.</exception>
        public T Retrieve<T>(string fileName) where T : class
        {
            if (_isDisposed)
            {
                throw new InvalidOperationException("This instance has already been disposed.");
            }

            if (string.IsNullOrEmpty(fileName))
            {
                return default(T);
            }

            if (_isolatedStorageFile.FileExists(fileName))
            {
                var file = _isolatedStorageFile.OpenFile(fileName, FileMode.Open, FileAccess.Read);
                using (var streamReader = new StreamReader(file, Encoding.UTF8))
                {
                    var serialized = streamReader.ReadToEnd();

                    // deserialize
                    var settings = CreateSerializerSettings();
                    var result = JsonConvert.DeserializeObject<T>(serialized, settings);
                    return result;
                }
            }
            else
            {
                return default(T);
            }
        }

        private static JsonSerializerSettings CreateSerializerSettings()
        {
            var settings = new JsonSerializerSettings
                               {
                                   DefaultValueHandling = DefaultValueHandling.Ignore, 
                                   NullValueHandling = NullValueHandling.Ignore, 
                                   ContractResolver = new IgnoreReadOnlyContractResolver()
                               };

            return settings;
        }

        #region IDisposable

        private bool _isDisposed;

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Releases unmanaged and - optionally - managed resources.
        /// </summary>
        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
        protected virtual void Dispose(bool disposing)
        {
            if (!_isDisposed)
            {
                if (disposing)
                {
                    // dispose managed resources.
                    if (_isolatedStorageFile != null)
                    {
                        _isolatedStorageFile.Dispose();
                        _isolatedStorageFile = null;
                    }
                }

                // release unmanaged resources
            }

            _isDisposed = true;
        }

        #endregion
    }
}